//===--- EnumImpl.h - Enum implementation runtime declarations --*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Enum implementation details declarations of the Swift runtime.
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_RUNTIME_ENUMIMPL_H
#define SWIFT_RUNTIME_ENUMIMPL_H

#include "swift/ABI/Enum.h"
#include "swift/Runtime/Enum.h"

namespace swift {

/// This is a small and fast implementation of memcpy with a constant count. It
/// should be a performance win for small constant values where the function
/// can be inlined, the loop unrolled and the memory accesses merged.
template <unsigned count> static void small_memcpy(void *dest, const void *src) {
  uint8_t *d8 = (uint8_t*)dest;
  const uint8_t *s8 = (const uint8_t*)src;
  for (unsigned i = 0; i < count; i++) {
    *d8++ = *s8++;
  }
}

static inline void small_memcpy(void *dest, const void *src, unsigned count,
                                bool countMaybeThree = false) {
  // This is specialization of the memcpy line below with
  // specialization for values of 1, 2 and 4.
  // memcpy(dst, src, count)
  if (count == 1) {
    small_memcpy<1>(dest, src);
  } else if (count == 2) {
    small_memcpy<2>(dest, src);
  } else if (countMaybeThree && count == 3) {
    small_memcpy<3>(dest, src);
  } else if (count == 4) {
    small_memcpy<4>(dest, src);
  } else {
    swift::crash("Tagbyte values should be 1, 2 or 4.");
  }
}

static inline void small_memset(void *dest, uint8_t value, unsigned count) {
  if (count == 1) {
    memset(dest, value, 1);
  } else if (count == 2) {
    memset(dest, value, 2);
  } else if (count == 4) {
    memset(dest, value, 4);
  } else {
    swift::crash("Tagbyte values should be 1, 2 or 4.");
  }
}

inline unsigned getEnumTagSinglePayloadImpl(
    const OpaqueValue *enumAddr, unsigned emptyCases, const Metadata *payload,
    size_t payloadSize, unsigned payloadNumExtraInhabitants,
    getExtraInhabitantTag_t *getExtraInhabitantTag) {

  // If there are extra tag bits, check them.
  if (emptyCases > payloadNumExtraInhabitants) {
    auto *valueAddr = reinterpret_cast<const uint8_t *>(enumAddr);
    auto *extraTagBitAddr = valueAddr + payloadSize;
    unsigned extraTagBits = 0;
    unsigned numBytes =
        getEnumTagCounts(payloadSize,
                         emptyCases - payloadNumExtraInhabitants,
                         1 /*payload case*/).numTagBytes;

#if defined(__BIG_ENDIAN__)
    small_memcpy(reinterpret_cast<uint8_t *>(&extraTagBits) + 4 - numBytes,
                 extraTagBitAddr, numBytes);
#else
    small_memcpy(&extraTagBits, extraTagBitAddr, numBytes);
#endif

    // If the extra tag bits are zero, we have a valid payload or
    // extra inhabitant (checked below). If nonzero, form the case index from
    // the extra tag value and the value stored in the payload.
    if (extraTagBits > 0) {
      unsigned caseIndexFromExtraTagBits =
          payloadSize >= 4 ? 0 : (extraTagBits - 1U) << (payloadSize * 8U);

#if defined(__BIG_ENDIAN__)
      // On BE high order bytes contain the index
      unsigned long caseIndexFromValue = 0;
      unsigned numPayloadTagBytes = std::min(size_t(8), payloadSize);
      if (numPayloadTagBytes)
        memcpy(reinterpret_cast<uint8_t *>(&caseIndexFromValue) + 8 -
               numPayloadTagBytes,
               valueAddr, numPayloadTagBytes);
#else
      // In practice we should need no more than four bytes from the payload
      // area.
      unsigned caseIndexFromValue = 0;
      unsigned numPayloadTagBytes = std::min(size_t(4), payloadSize);
      if (numPayloadTagBytes)
        small_memcpy(&caseIndexFromValue, valueAddr,
                     numPayloadTagBytes, true);
#endif
      unsigned noPayloadIndex =
          (caseIndexFromExtraTagBits | caseIndexFromValue) +
          payloadNumExtraInhabitants;
      return noPayloadIndex + 1;
    }
  }

  // If there are extra inhabitants, see whether the payload is valid.
  if (payloadNumExtraInhabitants > 0) {
    return getExtraInhabitantTag(enumAddr, payloadNumExtraInhabitants, payload);
  }

  // Otherwise, we have always have a valid payload.
  return 0;
}

inline void storeEnumTagSinglePayloadImpl(
    OpaqueValue *value, unsigned whichCase, unsigned emptyCases,
    const Metadata *payload, size_t payloadSize,
    unsigned payloadNumExtraInhabitants,
    storeExtraInhabitantTag_t *storeExtraInhabitantTag) {

  auto *valueAddr = reinterpret_cast<uint8_t *>(value);
  auto *extraTagBitAddr = valueAddr + payloadSize;
  unsigned numExtraTagBytes =
      emptyCases > payloadNumExtraInhabitants
          ? getEnumTagCounts(payloadSize,
                             emptyCases - payloadNumExtraInhabitants,
                             1 /*payload case*/).numTagBytes
          : 0;

  // For payload or extra inhabitant cases, zero-initialize the extra tag bits,
  // if any.
  if (whichCase <= payloadNumExtraInhabitants) {
    if (numExtraTagBytes != 0)
      small_memset(extraTagBitAddr, 0, numExtraTagBytes);

    // If this is the payload case, we're done.
    if (whichCase == 0)
      return;

    // Store the extra inhabitant.
    storeExtraInhabitantTag(value, whichCase, payloadNumExtraInhabitants,
                            payload);
    return;
  }

  // Factor the case index into payload and extra tag parts.
  unsigned noPayloadIndex = whichCase - 1;
  unsigned caseIndex = noPayloadIndex - payloadNumExtraInhabitants;
  unsigned payloadIndex, extraTagIndex;
  if (payloadSize >= 4) {
    extraTagIndex = 1;
    payloadIndex = caseIndex;
  } else {
    unsigned payloadBits = payloadSize * 8U;
    extraTagIndex = 1U + (caseIndex >> payloadBits);
    payloadIndex = caseIndex & ((1U << payloadBits) - 1U);
  }

    // Store into the value.
#if defined(__BIG_ENDIAN__)
  uint64_t payloadIndexBuf = static_cast<uint64_t>(payloadIndex);
  if (payloadSize >= 8) {
    memcpy(valueAddr, &payloadIndexBuf, 8);
    memset(valueAddr + 8, 0, payloadSize - 8);
  } else if (payloadSize > 0) {
    payloadIndexBuf <<= (8 - payloadSize) * 8;
    memcpy(valueAddr, &payloadIndexBuf, payloadSize);
  }
  if (numExtraTagBytes)
    small_memcpy(extraTagBitAddr,
                 reinterpret_cast<uint8_t *>(&extraTagIndex) + 4 -
                     numExtraTagBytes,
                 numExtraTagBytes);
#else
  unsigned numPayloadTagBytes = std::min(size_t(4), payloadSize);
  if (numPayloadTagBytes)
    small_memcpy(valueAddr, &payloadIndex, numPayloadTagBytes, true);
  if (payloadSize > 4)
    memset(valueAddr + 4, 0, payloadSize - 4);
  if (numExtraTagBytes)
    small_memcpy(extraTagBitAddr, &extraTagIndex, numExtraTagBytes);
#endif
}

} /* end namespace swift */

#endif /* SWIFT_RUNTIME_ENUMIMPL_H */
